﻿#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

//动态内存管理
//描述班级的学生的数学成绩
//int math[30];
//如果一个班级36个人呢，创建的这个空间不足，一个班28个人呢，创建的空间会浪费两个
//像这样向内存申请空间就显得不太灵活
//那么这时候就需要让程序员动态的自己申请空间 c/c++中就能这样做

//4个函数 --c语言中动态内存开辟的函数
//mallooc
//free
//calloc
//realloc

//malloc的使用得包含头文件
//#include<stdlib.h>
//void* malloc(size_t size);//size是指的内存块的大小 单位是字节
//开辟空间成功的话 返回的是这块空间的起始地址
// 失败的话就会返回NULL
//之所以用void* 来接收地址是因为就光向内存申请了多少字节的空间 并不清楚要存储的是什么类型的数据
//int main()
//{
//	//申请20个字节的空间 --存放5个整数
//	int* p =(int*) malloc(20); //
//	//不能用void*来接收是因为我们在写代码的时候要明白拿到内存空间去使用什么类型的数据
//	//用强制类型转换成int* 是因为前面接受的是int*的指针
//
//	//看是否这20个字节开辟成功
//	if (p == NULL)
//	{
//		perror("malloc");//提示报错 malloc函数报的错
//		return 1;
//	}
//	//使用空间
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		*(p + i) = i + 1;//把数据存放到这块内存中 1 2 3 4 5  其实这块连续的内存在使用的时候可以理解为数组
//	}
//
//	//释放内存 free--专门来做动态内存的释放和回收的
//	//void free(void* ptr);
//	//想要释放哪块空间，就把哪块空间的起始地址传给它
//	free(p);//把p指向的这块空间的使用权限还给了操作系统  但是p还在指向这块空间 这时候p就变成了野指针了
//	//这时候就需要赋一个空指针
//	p = NULL;//避免p成为野指针
//	return 0;
//}

////使用空间
//int i = 0;
//for (i = 0; i < 5; i++)
//{
//	*p = i + 1;
//	p++;
//}
////这时候使用完空间 p指向了这块空间的末尾 已经不是起始地址了 所以这个时候不能释放p了

//栈区可以创建数组 地址低到高；创建局部变量 地址高到低--> 这个也要看具体的编译器的实现 也有从低到高的
// 堆区按照开辟的字节数来存放任何类型的数据

//calloc和realloc
//void* calloc(size_t num, size_t size);
////函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间，并且把空间的每个字节初始化为0。
////向内存申请5个整型的空间
//#include<stdlib.h>
//int main()
//{
//	/*int * p = (int *)malloc(5 * sizeof(int));
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}*/
//
//	int* p =(int*) calloc(5, sizeof(int));//calloc在申请完空间后 会初始化这块空间为0 但是malloc并不会 malloc存的是随机值
//	//5个大小为4个字节的空间
//	if (p == NULL)
//	{
//		perror("calloc");//perror函数打印完参数部分的字符串后，再打印一个冒号和空格 再打印错误信息--在字符函数和字符串函数里边
//		return 1;
//	}
//
//	//可以打印出来calloc函数初始化的值
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		printf("%d ", *(p + i));
//	}
//
//	//释放空间
//	free(p);
//	p = NULL;
//
//	return 0;
//}
//如果想要初始化的就选用calloc函数 不想初始化的选用malloc函数 但是malloc函数肯定比calloc函数要快，因为malloc函数并没有进行初始化

////realloc函数
//void* realloc(void* ptr, size_t size);
////ptr是要调整的内存地址
////size是调整之后的新的大小
////返回值为调整之后的内存的起始地址
//int main()
//{
//	int* p = (int*)malloc(5 * sizeof(int));//向内存申请了20字节的空间
//	//初始化了值 1 2 3 4 5
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//
//	//使用空间
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		*(p + i) = i + 1;
//	}
//	
//	//希望将空间调整为40个字节
//	//p= (int*)realloc(p,40);//不能直接写成这样的原因是因为p万一调整失败 得到的返回值是空指针，那么p原来的内存空间的东西也找不到了
//	int* ptr = (int*)realloc(p, 40);//一般要用新的指针变量来接收
//	if (ptr != NULL)//调整成功
//	{
//		p = ptr;//确保调整成功了 把ptr再赋给p 接着让p来维护
//		//空间调整好了 可以使用40个字节的空间
//		int i = 0;
//		for (i = 5; i < 10; i++)//只给后边新增加的20个字节赋值
//		{
//			*(p + i) = i + 1;
//		}
//		//打印出来 40个字节存储的内容
//		for (i = 0; i < 10; i++)
//		{
//			printf("%d ", *(p + i));
//		}
//		free(p);//不想用这块空间
//		p = NULL;
//	}
//	else//调整失败
//	{
//		perror("realloc");//调整失败了 报个错
//		free(p);
//		p = NULL;
//	}
//	//情况1 在这20个字节的空间后边是存在20个字节的空间且未被使用
//	// 
//	//情况2 在这20个字节的空间后边存在着20个字节的空间但是部分已经被使用了 --意味着20个字节的空间后边不足有新的20个字节的空间
//	//情况2的第一步 在堆区的内存中找一块新的空间 并且满足新的大小要求 也就是40字节
//	//第二步将原来空间的数据拷贝到一份新的空间
//	//第三步 释放旧的空间
//	//第四步 返回新的内存空间的起始地址
//	// 
//	//情况3 空间调整失败了 返回的是NULL
//
//
//	return 0;
//}

//realloc函数还有一个特殊的用途
//realloc函数可以完成和malloc函数一样的功能
//int main()
//{
//	realloc(NULL, 20);//就等于malloc(20);
//	return 0;
//}

////malloc失败的案例
//#include<stdlib.h>  //X86环境下
//int main()
//{
//	int * p =(int*)malloc(INT_MAX);//参数为一个很大的数字
//	if (p == NULL)
//	{
//		perror("malloc()");
//	}
//	return 0;
//}

//常见的动态内存的错误
//对空指针的解引用操作
//#include<assert.h>
//int main()
//{
//	int* p = (int*)malloc(20);
//	//如果不去判断 
//	//*p = 20;//有可能会是空指针
//
//	//所以要进行判断
//	/*if (p == NULL)
//	{
//		perror("malloc()");
//		return 1;
//
//	}*/
//
//	//不做这个判断的话 可以使用assert断言一下
//	assert(p);
//	//一旦代码出问题 就会报错 不往下执行了
//
//	//如果开辟成功的话 就会往下执行这个解引用操作
//	*p = 20;
//	return 0;
//}

////对动态开辟空间的越界访问
//void test()
//{
//	int i = 0;
//	int* p = (int*)malloc(10 * sizeof(int));
//	if ( NULL == p)
//	{
//		exit(EXIT_FAILURE);
//
//	}
//	for (i = 0; i <= 10; i++)//所以说要对他的边界进行把控 
//	{
//		*(p + i) = i;//当i是10的时候越界访问
//	}
//	free(p);
//}
//int main()
//{
//	test();
//	return 0;
//}

////对非动态开辟内存使⽤free释放
//int main()
//{
//	int a = 10;//申请的空间在栈上，并不在堆上
//	int* p = &a;
//	//...
//	free(p);//在这free只能是释放动态申请的空间
//	p = NULL;
//	return 0;
//}

////使⽤free释放⼀块动态开辟内存的⼀部分
//int main()
//{
//	int* p = (int*)malloc(100);
//	if (p == NULL)
//	{
//		return 1;
//	}
//	//
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		*p = i + 1;
//		p++;//并没有存完所有的内存空间 只存了5个数据
//		//这时候的p并没有指向这块内存的起始位置 而是指到了第六个整型的位置
//	}
//	free(p);//释放空间 做不到释放部分的动态内存空间 p必须是这块内存的起始位置
//	p = NULL;
//	return 0;
//}

////对同⼀块动态内存多次释放
//void test()
//{
//	int* p = (int*)malloc(100);
//	//...
//	free(p);
//	free(p);//重复释放
//	//即使是重复释放也没有关系 前提是已经将这块内存置为NULL指针了  下面这样写
//	free(p);
//	p = NULL;
//	free(p);//重复释放
//}
//int main()
//{
//	test();
//	return 0;
//}

////动态开辟内存忘记释放（内存泄漏）
//void test()
//{
//	int* p = (int*)malloc(100);
//	if (NULL != p)
//	{
//		*p = 20;
//	}
//}
//int main()
//{
//	test();
//	//test函数结束后 已经找不到p指向的内存了，因为p在test函数里边是局部变量 出了作用域就销毁了 这个地址也就找不到了
//	//想释放的话也是释放不了的 不用这个p也不释放p 加上后边死循环 更释放不了p  这样的话就会造成内存泄漏的问题
//	while (1);//这个是死循环
//}//内存是一种资源 当你不用的时候一定要想着回收
//起始malloc/calloc/realloc申请的内存，如果不想使用 可以使用free释放
//如果没有使用free来释放，当程序运行结束的时候，也会由操作系统回收的
//尽量要做到
//1.谁(函数)申请的空间谁释放  malloc/free成对出现 
//2.如果不能释放，要告诉使用的人，记得释放

//void test()
//{
//	//申请空间
//	int* p = (int*)malloc(100);
//	//使用空间
//	int n = 3;
//	if (n > 0)
//	{
//		return;  //这是在malloc和free成对出现，已经申请空间了， 但是使用空间的时候已经提前返回了 并没有机会往下执行了
//	}
//	free(p);
//	p = NULL;
//}
//int main()
//{
//	test();
//	//
//	return 0;
//}

//动态内存经典笔试题目分析
#include<string.h>
//void GetMemory(char* p)//这接收的话得用二级指针 char** p
//{
//	//这要改成*p 才能放到str中
//	p = (char*)malloc(100);//有malloc申请空间 但是没有释放 1.内存泄漏
//	//malloc开辟了100字节的空间
//	//这时候把这100字节的起始地址赋给了p p不再是空指针了 p就指向了100字节的空间
//	//p一旦出这个函数 申请的内存就会还给操作系统
//}
//void Test(void)
//{
//	char* str = NULL;//创建了一个指针变量str 并放了一个空指针
//	GetMemory(str);//在这是传值调用 把str里边的值也就是NULL传给了p   //如果想要改对的话 就得&str传过去
//	strcpy(str, "hello world");//不打印 2.程序崩掉
//	//str是NUll指针 因为p在出作用域的时候指向的空间就已经还给操作系统了
//	//这个就是对空指针的解引用操作 程序崩溃
//	printf(str);
//	//使用完就可以释放 并且置为NULL了
//	//free(str);
//	//str = NULL;
//}
//int main()
//{
//	Test();
//	return 0;
//}

////除了上边那一种改的方式 还是一种
//char* GetMemory(char* p)
//{
//	p = (char*)malloc(100);//把这个malloc创建的空间的起始地址赋给了p
//	//通过返回值带回去
//	return p;//在这返回的是p指向的那块空间的起始地址
//}
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory(str);//在这用str接收返回的地址就可以了
//	strcpy(str, "hello world");
//	printf(str);
//	//最后释放 置为空
//	free(str);
//	str = NULL;
//}
//int main()
//{
//	Test();
//	return 0;
//}

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}